Side navigation
Allow users to explore content and features through a primary navigation structure with a clear visual hierarchy
#Examples
Ideal for applications with multiple sections and subsections, where a clear visual hierarchy is essential.
The side navigation consists of:
- Links: Clickable text or icons leading to different content areas.
- Icons: Visual representations of navigation items (especially important in the collapsed view).
- Chevrons: Expandable/collapsible indicators for nested navigation levels.
- Levels: Primary (top-level), secondary, and tertiary levels to organize content.
- Collapsed view: Can be used to collapse the navigation in mobile or tablet views.
#Basic usage
This is a basic example, where we have selected an item under "data privacy" and the rest of the settings are at the default values.
Best practices:
- The selected item should be visually distinct.
- Navigation structure should mirror the content's logical hierarchy.
- Ensure the collapsed view uses clear, recognizable icons for each top-level item.
// These states are kept outside the component because you would most likely want to persist
// these in something like local storage to work well will page reloads.
const [selectionId, setSelectionId] = useState<string | null>(
"dataprivacy-personal-data-types-phone-number"
);
const [viewState, setViewState] = useState<ViewState>();
const [expandedItems, setExpandedItems] = useState<string[]>();
return (
<>
<div style={{ height: "100vh" }}>
<SideNavigation
data-observe-key="side-nav"
mainItems={data.sideNavigation}
bottomItems={data.sideBottomNavigation}
selectionId={selectionId}
setSelectionId={setSelectionId}
viewState={viewState}
setViewState={setViewState}
expandedItems={expandedItems}
setExpandedItems={setExpandedItems}
searchHotkey="M"
/>
</div>
</>
);
#Falling back to default expansion
This example shows how the component falls back to showing expanded nodes that match with the selection if the expansion sent into the props are not properly showing the selected item. In this case, we select an item under "data privacy", but the expansion is set to showing something under QA. In this case, it will fall back to expanding the the nodes to show the selected item.
- Use Case: This behavior is important when the provided expansion state doesn't align with the user's selection. It ensures the user can easily see the context of their current selection.
- Best Practice: Clearly indicate the selected item, even when the navigation falls back to a default state. This includes a subtle animation to smoothly transition between expansion states.
// These states are kept outside the component because you would most likely want to persist
// these in something like local storage to work well will page reloads.
const [selectionId, setSelectionId] = useState<string | null>(
"dataprivacy-personal-data-types-phone-number"
);
const [viewState, setViewState] = useState<ViewState>();
const [expandedItems, setExpandedItems] = useState(["qa", "qa-spelling-v2"]);
return (
<>
<div style={{ height: "100vh" }}>
<SideNavigation
data-observe-key="side-nav"
mainItems={data.sideNavigation}
bottomItems={data.sideBottomNavigation}
selectionId={selectionId}
setSelectionId={setSelectionId}
viewState={viewState}
setViewState={setViewState}
expandedItems={expandedItems}
setExpandedItems={setExpandedItems}
/>
</div>
</>
);
#Using labels
Menu-items can have labels. They are rendered using the Badge
component. The label can have an optional icon, text, and tooltip.
- Use Case: To highlight new features, internal use or other contextual information.
- Best Practice:
- Keep labels concise and visually distinct (use the Badge component).
- If using icons, choose universally recognizable ones.
// These states are kept outside the component because you would most likely want to persist
// these in something like local storage to work well will page reloads.
const [selectionId, setSelectionId] = useState<string | null>(null);
const [viewState, setViewState] = useState<ViewState>();
const [expandedItems, setExpandedItems] = useState<string[]>();
// Copy and wrangle the basic example to show off how to set labels
const nav: NavigationItem[] = data.sideNavigation.slice(0, 6);
nav[0].labels = [{ type: "highlight1", icon: <IconFeatureFlag />, tooltip: "Feature Flag Xyz" }];
nav[1].labels = [{ type: "highlight3", text: "Internal", tooltip: "Internal use only" }];
nav[3].children![0].labels = [{ type: "highlight2", text: "Demo", tooltip: "Demo data" }];
nav[4].labels = [
{ type: "warning", text: "Beta", tooltip: "Feature is still work in progress" },
{ type: "highlight1", icon: <IconFeatureFlag />, tooltip: "Feature Flag Xyz" },
];
nav[5].labels = [{ type: "highlight2", text: "Demo", tooltip: "Demo data" }];
return (
<>
<div style={{ height: "35rem" }}>
<SideNavigation
data-observe-key="side-nav"
mainItems={nav}
bottomItems={[]}
selectionId={selectionId}
setSelectionId={setSelectionId}
viewState={viewState}
setViewState={setViewState}
expandedItems={expandedItems}
setExpandedItems={setExpandedItems}
/>
</div>
</>
);
#Links with onClick handlers
This example has items with both href's but also onclick handlers. The idea is that simple clicks will be handled with the "onClick" handler and "preventDefault" so that the browser doesn't navigate. But if you "open in new tab" click, then the navigation happens.
- Use Case: This pattern is useful when you need to trigger specific actions in addition to, or instead of, navigating to a new page.
- Best Practice:
- Clearly communicate to the user whether a click will result in navigation or a different action.
- If the custom action fails, provide a fallback to ensure the user can still navigate to the destination.
// These states are kept outside the component because you would most likely want to persist
// these in something like local storage to work well will page reloads.
const [selectionId, setSelectionId] = useState<string | null>(null);
const [viewState, setViewState] = useState<ViewState>();
const [expandedItems, setExpandedItems] = useState<string[]>();
const items: NavigationItem[] = [
{
children: [],
defaultChildId: null,
id: "dashboard",
title: "Dashboards",
href: "#",
openNew: false,
icon: '\r\n\t\t\t\t\t<svg width="24" height="24" viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">\r\n\t\t\t\t\t\t<path d="M528-624v-192h288v192H528ZM144-432v-384h288v384H144Zm384 288v-384h288v384H528Zm-384 0v-192h288v192H144Zm72-360h144v-240H216v240Zm384 288h144v-240H600v240Zm0-479h144v-49H600v49ZM216-216h144v-48H216v48Zm144-288Zm240-191Zm0 239ZM360-264Z"></path>\r\n\t\t\t\t\t</svg>\r\n\t\t\t\t',
observeKey: "side-nav-dashboard",
labels: [],
visible: true,
},
{
children: [
{
children: [],
defaultChildId: null,
id: "dci-overview",
title: "DCI overview",
href: "#",
openNew: false,
icon: null,
observeKey: "side-nav-dci-overview",
labels: [],
visible: true,
},
{
children: [],
defaultChildId: null,
id: "dci-account-overview",
title: "My sites",
href: "#",
openNew: false,
icon: null,
observeKey: "side-nav-dci-account-overview",
labels: [],
visible: true,
},
{
children: [],
defaultChildId: null,
id: "dci-sites-progress",
title: "Sites progress",
href: "#",
openNew: false,
icon: null,
observeKey: "side-nav-dci-sites-progress",
labels: [
{
type: "highlight1",
icon: <IconFeatureFlag />,
tooltip: "The feature is hidden behind the Feature Flag: RMDCISiteProgress",
},
],
visible: true,
},
],
defaultChildId: "dci-overview",
id: "dci",
title: "Digital Certainty Index",
href: "#",
openNew: false,
icon: '\r\n\t\t\t\t\t<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">\r\n\t\t\t\t\t\t<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0127 3.99743L12.0114 3.99885C12.1595 3.99906 12.3075 4.00337 12.4554 4.0118C13.1287 4.05017 13.799 4.17377 14.4499 4.38308C16.198 4.94522 17.6995 6.09142 18.7027 7.62939C19.7059 9.16735 20.1497 11.0035 19.9596 12.8299C19.7695 14.6562 18.9571 16.3617 17.6587 17.6601C16.3603 18.9585 14.6548 19.7709 12.8285 19.961C11.0021 20.1511 9.16593 19.7073 7.62796 18.7041C6.09 17.7009 4.9438 16.1994 4.38166 14.4513C3.93884 13.0743 3.87966 11.6103 4.19954 10.2142C4.28572 9.83802 4.39943 9.46677 4.54058 9.10346L4.90224 8.17253L3.04038 7.4492L2.67872 8.38013C2.50236 8.83408 2.36027 9.29799 2.25256 9.76809C1.85285 11.5127 1.92677 13.3421 2.48013 15.0628C3.18259 17.2472 4.61487 19.1235 6.53671 20.3771C8.45856 21.6307 10.753 22.1852 13.0353 21.9477C15.3175 21.7101 17.4486 20.695 19.0711 19.0725C20.6935 17.45 21.7087 15.3189 21.9463 13.0367C22.1838 10.7545 21.6293 8.45998 20.3757 6.53814C19.1221 4.61629 17.2458 3.18401 15.0614 2.48156C14.2481 2.22002 13.4105 2.06556 12.569 2.01761C12.3842 2.00708 12.1992 2.00168 12.0143 2.00142L11.0156 2L11.0127 3.99743Z"/>\r\n\t\t\t\t\t</svg>\r\n\t\t\t\t',
observeKey: "side-nav-dci",
labels: [],
visible: true,
},
];
return (
<>
<div style={{ height: "35rem" }}>
<SideNavigation
data-observe-key="side-nav"
mainItems={items}
bottomItems={[]}
selectionId={selectionId}
setSelectionId={setSelectionId}
viewState={viewState}
setViewState={setViewState}
expandedItems={expandedItems}
setExpandedItems={setExpandedItems}
/>
</div>
</>
);
#One main nav item and fixed view
If there is only one main nav item in mainItems
the sub nav will be shown on load and the "Back to main navigation" button will be replaced by a back link that uses the props backLink
and backLinkLabel
. This can be combined with viewState="fixed"
which will hide the view state toggle button to collapse and expand the navigation.
- Use Case: This is perfect for applications with a single primary section and a fixed secondary navigation structure (E.g Page Inspector)
- Best Practice: In this scenario, ensure the back link is clearly visible and labeled. Since the navigation is always visible, make sure it doesn't obscure or overlap with the main content area.
// These states are kept outside the component because you would most likely want to persist
// these in something like local storage to work well will page reloads.
const [selectionId, setSelectionId] = useState<string | null>(null);
const [viewState, setViewState] = useState<ViewState>("fixed");
const [expandedItems, setExpandedItems] = useState<string[]>();
return (
<>
<div style={{ height: "75vh" }}>
<SideNavigation
data-observe-key="side-nav"
mainItems={dataOneMainNavItem}
bottomItems={[]}
selectionId={selectionId}
setSelectionId={setSelectionId}
viewState={viewState}
setViewState={setViewState}
expandedItems={expandedItems}
setExpandedItems={setExpandedItems}
backButtonLabel="Back to main navigation"
backButtonUrl="http://www.siteimprove.com"
/>
</div>
</>
);
#Properties
#Guidelines
#Best practices
#Do not use when
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications